//
// Copyright (C) 2011 Solar Technology Ltd,
// All rights reserved.
//
// Author: Finux Chen <finuxchen@gmail.com>
//
// This file is part of customized widgets for touch screen.
//
#include "touchspinbox.h"
#include <QtGui>
#include <limits>
#include <cstdlib>
#include <cmath>
#include "touchlineedit.h"

// TouchSpinBoxBase ////////////////////////////////////////////////////////////


TouchSpinBoxBase::TouchSpinBoxBase(QWidget *parent /* = 0 */)
	: QWidget(parent),
	timerTarget(kTimerPlus),
	timer_interval_(500),
	value_(0),
	max_(std::numeric_limits<int>().max()),
	min_(std::numeric_limits<int>().min()),
	step_(1)
{
	InitWidget();

	connect(plusButton, SIGNAL(pressed()), this, SLOT(spinPressed()));
	connect(minusButton, SIGNAL(pressed()), this, SLOT(spinPressed()));
	connect(plusButton, SIGNAL(released()), this, SLOT(spinReleased()));
	connect(minusButton, SIGNAL(released()), this, SLOT(spinReleased()));
	connect(plusButton, SIGNAL(clicked()), this, SLOT(plus()));
	connect(minusButton, SIGNAL(clicked()), this, SLOT(minus()));
	connect(spinTimer, SIGNAL(timeout()), this, SLOT(timerTimeOut()));
	connect(valueEdit, SIGNAL(editingFinished()), this, SLOT(checkValueText()));
	connect(valueEdit, SIGNAL(textEdited(const QString &)), 
		this, SLOT(captureInput(const QString &)));
}

TouchSpinBoxBase::~TouchSpinBoxBase()
{
	delete inputValidator;
	delete signLabel;
	delete valueEdit;
	delete plusButton;
	delete minusButton;
	delete hboxLayout;
	delete vboxLayout;
	delete spinTimer;
}

void TouchSpinBoxBase::setValue(int value)
{
	value_ = value;
	updateText();
}

void TouchSpinBoxBase::setValueString(const QString &text)
{
	valueEdit->setText(text);
	updateValue();
}

void TouchSpinBoxBase::setSignString(const QString &text)
{
	signLabel->setText(text);
	updateValue();
}

void TouchSpinBoxBase::plus()
{
	unsigned int difference = std::abs(max_ - value_);
	if(difference >= (unsigned int)step_){
		value_ += step_;
		updateText();
		emit valueChanged();
	}
}
void TouchSpinBoxBase::minus()
{
	unsigned int difference = std::abs(value_ - min_);
	if(difference >= (unsigned int)step_){
		value_ -= step_;
		updateText();
		emit valueChanged();
	}
}

void TouchSpinBoxBase::spinPressed()
{
	QPushButton *button = qobject_cast<QPushButton *>(sender());
	timerTarget = (button == plusButton) ? kTimerPlus : kTimerMinus;
	spinTimer->start(timer_interval_);
}
void TouchSpinBoxBase::spinReleased()
{
	spinTimer->stop();
	timer_interval_ = 500;
}
void TouchSpinBoxBase::timerTimeOut()
{
	if(kTimerPlus == timerTarget){
		plus();
	}else{
		minus();
	}
	if(timer_interval_ > 400){
		timer_interval_ = 100;
	}else if(timer_interval_ > 60){
		timer_interval_ -= 5;
	}else if(timer_interval_ > 40){
		timer_interval_ -= 10;
	}else if(timer_interval_ > 5){
		timer_interval_ -= 1;
	}else{
		timer_interval_ = 0;
	}
	spinTimer->setInterval(timer_interval_);
}

void TouchSpinBoxBase::checkValueText()
{
	updateValue();
	emit valueChanged();
}

void TouchSpinBoxBase::InitWidget()
{
	signLabel   = new QLabel("+");
	//valueEdit   = new QLineEdit;
	valueEdit   = new TouchLineEdit;
	plusButton  = new QPushButton("+");
	minusButton = new QPushButton("-");
	valueEdit->setMaxLength(std::numeric_limits<int>().digits10 + 1 + 1);
	valueEdit->setText("0");

	QSizePolicy labelSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
	signLabel->setAlignment(Qt::AlignCenter);
	signLabel->setSizePolicy(labelSizePolicy);

	QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
	valueEdit->setAlignment(Qt::AlignCenter);
	valueEdit->setSizePolicy(sizePolicy);
	plusButton->setSizePolicy(sizePolicy);
	minusButton->setSizePolicy(sizePolicy);

	hboxLayout = new QHBoxLayout;
	hboxLayout->addWidget(signLabel);
	hboxLayout->addWidget(valueEdit);
	vboxLayout = new QVBoxLayout;
	vboxLayout->addWidget(plusButton);
	vboxLayout->addLayout(hboxLayout);
	vboxLayout->addWidget(minusButton);
	setLayout(vboxLayout);

	spinTimer = new QTimer;
}

QString TouchSpinBoxBase::valueTextFromAbs()
{
	return QString::number(std::abs(value_));
}

QString TouchSpinBoxBase::lineEditText()
{
	return valueEdit->text();
}
QString TouchSpinBoxBase::signText()
{
	return (value_ < 0 ? "-" : "+");
}
void TouchSpinBoxBase::updateText()
{
	valueEdit->setText(valueTextFromAbs());
	signLabel->setText(signText());
//	emit valueChanged();
}
unsigned int TouchSpinBoxBase::absValueFromText()
{
	return valueEdit->text().toUInt();
}
int TouchSpinBoxBase::valueFromText()
{
	if(signLabel->text() == "+" && 
		absValueFromText() > (unsigned int)std::numeric_limits<int>().max()){
		// upper overflow
		//throw std::overflow_error;
		throw("we're in trouble!");
	}else if(signLabel->text() == "-" &&
		absValueFromText() > std::abs(std::numeric_limits<int>().min())){
		// lower overflow
		//throw std::overflow_error();
		throw("we're in trouble!");
	}

	int value = (int)((signLabel->text() == "+") ? 
		absValueFromText() : 0 - absValueFromText());

	if(value > max_ || value < min_){
		throw("we're in trouble!");
	}

	return value;
}
void TouchSpinBoxBase::updateValue()
{
	try{
		value_ = valueFromText();
//		emit valueChanged();
	}catch(...){
		updateText();
	}
}

int TouchSpinBoxBase::calcMaxLength()
{
	unsigned int abs_max = 
		std::abs(max_) > std::abs(min_) ? std::abs(max_) : std::abs(min_);
	int length = 1;
	while(abs_max > 9){
		abs_max /= 10;
		++length;
	}
	return length;
}
void TouchSpinBoxBase::captureInput(const QString &text)
{
	qDebug() << "captured input : " << text;
	if(text.contains("-")){
		int cursor_pos = valueEdit->cursorPosition();
		signLabel->setText(signLabel->text() == "+" ? "-" : "+");
		int sign_index = text.indexOf("-");
		QString abs_num_str = text.left(sign_index) 
							+ text.right(text.length() - sign_index - 1);
		valueEdit->setText(abs_num_str);
		valueEdit->setCursorPosition(cursor_pos - 1);
	}
	if(!text.isEmpty()){
		updateValue();
		emit valueChanged();
	}
}


// TouchSpinBox ////////////////////////////////////////////////////////////////

TouchSpinBox::TouchSpinBox(QWidget *parent)
	: TouchSpinBoxBase(parent)
{
	QRegExp regx("[0-9, -]+$");
	inputValidator = new QRegExpValidator(regx, valueEdit);
	valueEdit->setValidator(inputValidator);
}


// TouchDecimalSpinBox /////////////////////////////////////////////////////////


TouchDecimalSpinBox::TouchDecimalSpinBox(QWidget *parent /* = 0 */)
	: TouchSpinBoxBase(parent),
	max_length_(std::numeric_limits<int>().digits10 + 1)
{
	min_ = 0;

	QRegExp regx("[0-9]+$");
	inputValidator = new QRegExpValidator(regx, valueEdit);
	valueEdit->setValidator(inputValidator); 
	
	signLabel->setVisible(false);
}

void TouchDecimalSpinBox::setDoubleStep(double d_step)
{
	d_step_ = d_step;
	double double_step = d_step;
	int length = 0;
	while(double_step < 1){
		double_step *= 10;
		++length;
	}
	max_length_ = length;
	step_ = (int) double_step;
	max_ = std::pow(10, length) - 1;

	valueEdit->setMaxLength(max_length_);
}

void TouchDecimalSpinBox::checkValueText()
{
	while(valueEdit->text().length() < max_length_){
		valueEdit->setText(valueEdit->text()+"0");		
	}
	updateValue();
	emit valueChanged();
}

QString TouchDecimalSpinBox::valueTextFromAbs()
{
	QString valueText = QString::number(std::abs(value_));
	while(valueText.length() < max_length_){
		valueText = "0" + valueText;
	}
	return valueText;
}

int TouchDecimalSpinBox::calcMaxLength()
{
	double d_step = d_step_;
	int length = 0;
	while(d_step < 1){
		d_step *= 10;
		++length;
	}
	return length;
}

// TouchDoubleSpinBox //////////////////////////////////////////////////////////

TouchDoubleSpinBox::TouchDoubleSpinBox(QWidget *parent /* = 0 */)
	: QWidget(parent),
	value_(0.0),
	step_(0.1),
	max_(std::numeric_limits<double>().max()),
	min_(std::numeric_limits<double>().min())
{
	integerSpinBox = new TouchSpinBox;
	decimalSpinBox = new TouchDecimalSpinBox;
	hboxLayout = new QHBoxLayout;
	hboxLayout->addWidget(integerSpinBox);
	hboxLayout->addWidget(decimalSpinBox);
	setLayout(hboxLayout);

	connect(integerSpinBox, SIGNAL(valueChanged()), this, SLOT(checkValue()));
	connect(decimalSpinBox, SIGNAL(valueChanged()), this, SLOT(checkValue()));
}

TouchDoubleSpinBox::~TouchDoubleSpinBox()
{
	delete integerSpinBox;
	delete decimalSpinBox;
	delete hboxLayout;
}

void TouchDoubleSpinBox::setValue(double value)
{
	integerSpinBox->setSignString(value < 0 ? "-" : "+");
	QString num_str = QString::number(std::abs(value));
	if(num_str.indexOf(".") != -1){
		integerSpinBox->setValueString(num_str.left(num_str.indexOf(".")));
		decimalSpinBox->setValueString(
			num_str.right(num_str.length() - num_str.indexOf(".") - 1));
	}else{
		integerSpinBox->setValueString(num_str);
		decimalSpinBox->setValue(0);
	}
}


void TouchDoubleSpinBox::setMaximum(double max)
{
	max_ = max;
	integerSpinBox->setMaximum((int)max_);
}

void TouchDoubleSpinBox::setMinimum(double min)
{
	min_ = min;
	integerSpinBox->setMinimum((int)min_);
}


void TouchDoubleSpinBox::setRange(double min, double max)
{
	setMinimum(min);
	setMaximum(max);
}

void TouchDoubleSpinBox::setStep(double step)
{
	decimalSpinBox->setDoubleStep(step);
}

void TouchDoubleSpinBox::checkValue()
{
	QString abs_str = integerSpinBox->lineEditText() 
					+ "." + decimalSpinBox->lineEditText();
	double value = abs_str.toDouble();
	if(integerSpinBox->signText() == "-"){
		value = 0 - value;
	}
	if(value > max_ || value < min_){
		setValue(value_);	
	}else{
		value_ = value;
	}
}
